// Game.h
//
// Created By: Jeremy Butler
// Created On: 23-Mar-2011

#include "Game.h"
#include "ChessUtils.h"

#include <cassert>
using namespace std;

Game::Game() : board(),
	       selectedSpace(-1,-1),
	       selectedSpaceMoves(),
	       moveHistory(),
	       b_blackInCheck(),
	       b_whiteInCheck(),
	       b_blackInCheckMate(),
	       b_whiteInCheckMate(),
	       b_staleMate(){
  return;
}

Game::Game(const Game & other) : board(other.board),
				 selectedSpace(other.selectedSpace),
				 selectedSpaceMoves(other.selectedSpaceMoves),
				 moveHistory(other.moveHistory),
				 b_blackInCheck(other.b_blackInCheck),
				 b_whiteInCheck(other.b_whiteInCheck),
				 b_blackInCheckMate(other.b_blackInCheckMate),
				 b_whiteInCheckMate(other.b_whiteInCheckMate),
				 b_staleMate(other.b_staleMate){
  return;
}

Game::Game(const vector<pair<Piece *, Location> > & pieces, const deque<Move> & history) :
    board(),
    selectedSpace(-1,-1),
    selectedSpaceMoves(),
    moveHistory(history),
    b_blackInCheck(false),
    b_whiteInCheck(false),
    b_blackInCheckMate(false),
    b_whiteInCheckMate(false),
    b_staleMate(false){

  int row, col;
  for(int i = 0; i < pieces.size(); i++){
    row = pieces[i].second.GetY();
    col = pieces[i].second.GetX();

    board.AddPiece(*(pieces[i].first),row,col);
  }

  Move * move = NULL;

  if(!moveHistory.empty())
    move = &(moveHistory.back());
    
  UpdateGameState(move);
}

Game & Game::operator = (const Game & other){
  if(this != &other){
    board = other.board;
    selectedSpace = other.selectedSpace;
    selectedSpaceMoves = other.selectedSpaceMoves;
    moveHistory = other.moveHistory;
    b_blackInCheck = other.b_blackInCheck;
    b_whiteInCheck = other.b_whiteInCheck;
    b_blackInCheckMate = other.b_blackInCheckMate;
    b_whiteInCheckMate = other.b_whiteInCheckMate;
    b_staleMate = other.b_staleMate;
  }
  return *this;
}

Game::~Game(){
  return;
}

const Piece * Game::operator() (int row, int col) const{
  return board(row,col);
}

const Move * Game::MovePiece(int row, int col, PieceType newPieceType){
  Location temp(col,row);
  if(selectedSpaceMoves.find(temp) != selectedSpaceMoves.end()){
    const Piece * takenPiece = board(row,col);
    Piece * newPiece = NULL;

    if(newPieceType != NO_TYPE)
      newPiece = PieceFactory(board(selectedSpace.GetY(),selectedSpace.GetX())->GetColor(),
			      newPieceType);
    
    int fromRow = selectedSpace.GetY();
    int fromCol = selectedSpace.GetX();
    Move move(*(board(fromRow,fromCol)), fromRow, fromCol,
	      newPiece, row, col, takenPiece);
    board.MovePiece(fromRow,fromCol,row,col);
    SetSelectedSpace(fromRow,fromCol);

    UpdateGameState(&move);
    moveHistory.push_back(move);
    return &moveHistory.back();
  }
  return NULL;
}

void Game::SetSelectedSpace(int row, int col){
  if(row < 8 && row > -1 && col < 8 && col > -1){
    selectedSpace.Set(col,row);
    selectedSpaceMoves = board.GetValidMoves(row,col);
  }
  else{
    selectedSpace.Set(-1,-1);
    selectedSpaceMoves.clear();
  }
    
}

const Location & Game::GetSelectedSpace() const{
  return selectedSpace;
}

const set<Location> Game::GetValidMoves(int row, int col) const{
  if(selectedSpace.GetY() == row && selectedSpace.GetX() == col)
    return selectedSpaceMoves;
  else
    return board.GetValidMoves(row,col);
}

Move * Game::UndoMove(){
  if(!moveHistory.empty()){
    Move * move = new Move(moveHistory.back());
    int fromRow = move->GetFromLocation().GetY();
    int fromCol = move->GetFromLocation().GetX();
    int toRow = move->GetToLocation().GetY();
    int toCol = move->GetToLocation().GetX();
    const Piece * replace = move->GetTakenPiece();

    board.MovePiece(toRow,toCol,fromRow,fromCol,move->GetFromPiece()->GetType());
    if(replace)
      board.AddPiece(*replace,toRow,toCol);

    moveHistory.pop_back();
    selectedSpace.Set(-1,-1);
    if(b_blackInCheckMate)
      b_blackInCheckMate = false;
    if(b_whiteInCheckMate)
      b_whiteInCheckMate = false;
    if(b_staleMate)
      b_staleMate = false;
    return move;
  }
  return NULL;
}

//! @brief Clear the board of all pieces and move history.
void Game::Clear(){
  board.Clear();
}

bool Game::IsInCheck(Color color) const{
  if(color == WHITE)
    return b_whiteInCheck;
  else if(color == BLACK)
    return b_blackInCheck;

  return false;
}

bool Game::IsInCheckMate(Color color) const{
  if(color == WHITE)
    return b_whiteInCheckMate;
  else if(color == BLACK)
    return b_blackInCheckMate;

  return false;
}

bool Game::StaleMate() const{
  return b_staleMate;
}

void Game::UpdateGameState(const Move * lastMove){
  bool check = false;
  Color nextTurnColor = WHITE;
  if(lastMove != NULL && lastMove->GetToPiece()->GetColor() != WHITE){
    check = board.IsInCheck(WHITE);
  }
  else if(lastMove != NULL && lastMove->GetToPiece()->GetColor() == WHITE){
    nextTurnColor = BLACK;
    check = board.IsInCheck(BLACK);
  }
  else if(lastMove == NULL){
    if(board.IsInCheck(WHITE) && board.IsInCheck(BLACK))
      throw IllegalStateException("Both kings cannot be in check");
  }
  
  if(check){
    SetCheckAndMate(nextTurnColor);
  }
  else if(board.IsStaleMate(nextTurnColor))
    b_staleMate = true;
  else{
    if(nextTurnColor == WHITE)
      b_whiteInCheck = false;
    else if(nextTurnColor == BLACK)
      b_blackInCheck = false;
  }
}

void Game::SetCheckAndMate(Color nextTurnColor){

    if(nextTurnColor == WHITE)
      b_whiteInCheck = true;
    else if(nextTurnColor == BLACK)
      b_blackInCheck = true;

    if(board.IsInCheckMate(nextTurnColor)){

      if(nextTurnColor == WHITE)
	b_whiteInCheckMate = true;
      else if(nextTurnColor == BLACK)
	b_blackInCheckMate = true;

    }
}

const set<Piece *> & Game::GetPieces(Color color) const{
  return board.GetPieces(color);
}

const Location Game::GetPieceLocation(const Piece * piece) const{
  return board.GetPieceLocation(piece);
}

const Board & Game::GetBoard() const{
  return board;
}

const deque<Move> & Game::GetHistory() const{
  return moveHistory;
}